'''Filters for generating lists of test cases according to various filters

This seems to work pretty good, but I'm not yet convinced this is
the right API. For example, for a tag search I want to supply 
additional arguments to tell it whether to consider or ignore
"force tags". 
'''

import sys
import robot.parsing.model
import robot.errors
import re

# constants that external callers can use to define the type
# of match to perform
STRING="string"
REGEX = "regex"

class RobotFilter(object):
    '''Abstract class. Subclasses need to define a method named _match'''
    def __init__(self, suite):
        self.suite = suite
    
    def filter(self, pattern=None, filter_type = STRING):
        if filter_type == REGEX:
            pattern = re.compile(pattern)
        for result in self._do_filter(self.suite, pattern, filter_type):
            yield result

    def _do_filter(self, suite, pattern, filter_type):
        if self._match(suite, pattern, filter_type):
            yield suite
        for test in suite.testcase_table:
            if self._match(test, pattern, filter_type):
                yield test
        for keyword in suite.keyword_table:
            if self._match(keyword, pattern,filter_type):
                yield keyword
        for child_suite in suite.children:
            for result in self._do_filter(child_suite, pattern, filter_type):
                yield result

    def _match(self, obj, pattern, filter_type):
        raise NotImplementedError("_match must be defined by a subclass")

class TestCaseTagFilter(RobotFilter):
    '''Filter tests by tag name

    This only matches against tags directly in the test case; it doesn't,
    for example, look at force tags (should it?)
    '''
    def _match(self, obj, pattern, filter_type):
        if pattern.strip() == "":
            # null pattern? Everything matches
            return True

        if isinstance(obj, robot.parsing.model.TestCase):
            tags = GetTags(obj)
            if filter_type == STRING:
                if tags:
                    matches = filter(lambda tag: pattern in tag, tags)
                    return len(matches) > 0
            elif filter_type == REGEX:
                if obj.tags.value:
                    matches = filter(lambda tag: pattern.match(tag), tags)
                    return len(matches) > 0
        return False

    def _get_tags(self,obj):
        if not hasattr(obj, "tags"):
            return []

        tags = []
        if obj.tags.value:
            tags = obj.tags.value
        else:
            # man, there _really_ needs to be a robot API to do this...
            # no tags? Get default tags of parent. (actually, parent.parent,
            # since the parrent points to a testcase_table rather than a 
            # test suite file)
            default_tags = obj.parent.parent.setting_table.default_tags.value
            if default_tags:
                tags += default_tags

        for ancestor in GetAncestors(obj):
            force_tags = ancestor.setting_table.force_tags.value
            if force_tags:
                tags += force_tags

        return tags
    

class TestCaseNameFilter(RobotFilter):
    '''Return a list of tests whose name contains a given pattern

    Returns a list of tuples. Each tuple is a set of references
    to a test case and it's ancestor suites (with the test case
    always being the last element)
    '''
    def _match(self, obj, pattern, filter_type):
        if pattern.strip() == "":
            # null pattern? Everything matches
            return True

        if isinstance(obj, robot.parsing.model.TestCase):
            if filter_type == STRING and pattern in obj.name:
                return True
            elif filter_type == REGEX and pattern.search(obj.name) is not None:
                return True
        return False

class NullFilter(RobotFilter):
    def _match(self, *args):
        return True

def GetAncestors(obj):
    '''Return a list of ancestor test suite objects for a test case or suite'''
    result = []
    parent = obj.parent
    while parent:
        # The robot object model doesn't have a simple parent/child
        # relationship between suites and test cases.  The parent of a
        # test case is a test case table rather than a test suite. 
        # This only keeps track of suites and testcases, and not the
        # intermediate table objects
        if (isinstance(parent, robot.parsing.model.TestDataDirectory) or 
            isinstance(parent, robot.parsing.model.TestCaseFile) or 
            isinstance(parent, robot.parsing.model.TestCase)):
            result.append(parent)
        parent = parent.parent
    return result

def GetTags(obj):
    '''Return the tags for a test case

    Returns an empty list of obj doesn't have the 'tags' attribute.
    
    This follows robot's rules for parsing -- if the tags are undefined,
    it returns the default tags of the parent suite. It also includes
    the force tags of all parent suites.
    '''
    if not hasattr(obj, "tags"):
        return []

    tags = []
    if obj.tags.value:
        tags = obj.tags.value
    else:
        default_tags = obj.parent.parent.setting_table.default_tags.value
        if default_tags:
            tags += default_tags

    for ancestor in GetAncestors(obj):
        force_tags = ancestor.setting_table.force_tags.value
        if force_tags:
            tags += force_tags

    return tags

if __name__ == "__main__":
    for suite_name in sys.argv[1:]:
        suite = robot.parsing.model.TestData(parent=None, source=suite_name)
        f = TestCaseTagFilter(suite)
        for result in f.filter("", REGEX):
            full_path = GetAncestors(result) + [result]
#            print "=>", result
            print "=>", ".".join([x.name for x in full_path])
        
